package CountingSort;


import com.sun.j3d.utils.behaviors.vp.OrbitBehavior;
import com.sun.j3d.utils.geometry.Box;
import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.utils.universe.SimpleUniverse;
import java.awt.BorderLayout;
import java.awt.GraphicsConfiguration;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.media.j3d.Appearance;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.ColoringAttributes;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.swing.JFrame;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3f;


/**
 *
 * @author rafael
 */
public class Main {

    public static float raio = 0.2f;
    private static int numero = 7; //Elementos
    public static float distancia = 0.0f; //Espaçamento entre os objetos.
    public static SimpleUniverse su;
    public static Number[] vetorNumeros = new Number[numero]; //Vetor de numeros.

    public static JFrame addJanela() {

        JFrame frame = new JFrame("Counting Sort");
        //Define as configurações da janela, no caso, o Layout.
        frame.getContentPane().setLayout(new BorderLayout());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //Aqui pega as configurações do SimpleUniverse.
        GraphicsConfiguration gConfig = SimpleUniverse.getPreferredConfiguration();

        //Aqui, cria o canvas3D que vai conter o universo no final.
        Canvas3D canvas = new Canvas3D(gConfig);
        frame.getContentPane().add("Center", canvas);

        //Aqui, cria o BG que vai entrar no SU contendo as configurações do Canvas.
        BranchGroup bgCena = addBranchGroup();
        BranchGroup bgCena2 = addBranchGroupEsferas();
        su = new SimpleUniverse(canvas);

        //Move a plataforma para o background para os objetos serem vistos.
        su.getViewingPlatform().setNominalViewingTransform();
        
        su.addBranchGraph(bgCena);
        su.addBranchGraph(bgCena2);

        //Aqui, define o tamanho da janela e ativa sua visibilidade.
        frame.setSize(1200, 740);
        frame.setVisible(true);

        //Aqui define que o universo possa girar e ser alterado de sua posição pelo mouse.
        OrbitBehavior orbital = new OrbitBehavior(canvas);
        orbital.setSchedulingBounds(new BoundingSphere(new Point3d(0.0,0.0,0.0),Double.MAX_VALUE));
        su.getViewingPlatform().setViewPlatformBehavior(orbital);

        return frame;
    }

    public static Appearance addAparencia(){

        Appearance aparencia = new Appearance();
        //Aqui, faz com que a aparencia possa ser alterada, caso necessário.
        aparencia.setCapability(Appearance.ALLOW_COLORING_ATTRIBUTES_READ);
        aparencia.setCapability(Appearance.ALLOW_COLORING_ATTRIBUTES_WRITE);

        //Cria o vetor da cor.
        Color3f cor = new Color3f(0.3f, 0.5f, 0.8f);

        //Aqui, define a cor da aparencia, no caso a da esfera.
        aparencia.setColoringAttributes(new ColoringAttributes(cor, numero));

        return aparencia;
    }

    private static BranchGroup addBranchGroup() {

        int i;
        BranchGroup branchGroup = new BranchGroup();

        //Aqui, joga a aparencia das esferas na Aparencia atual.
        Appearance aparencia = addAparencia();

        //Aqui vai definir a posicao de cada esfera.
        float posicao = -(raio * numero - raio);
        
        //Aqui, recebe o número de elementos do vetor.
        int j = numero;


        //Aqui, é utilizado um for para criar as esferas em sequência.
        for(i = 0; i < numero; i++){
            
            //Cria as esferas com o mesmo raio e a aparência recebida acima.
            Box caixa = new Box(raio, raio, raio, aparencia);

            //Aqui, permite que a esfera possa se mover/ser escrita, entre outros.
            caixa.setCapability(Box.ENABLE_APPEARANCE_MODIFY);

            //Aqui, cria os transforms3D de cada esfera e define seu posicionamento,
            Transform3D tfCaixa = new Transform3D(); // logo após, incrementando a distância para a próxima esfera.
            tfCaixa.setTranslation(new Vector3f(posicao, 0.3f, 0.0f));
            posicao += raio * 2 + distancia;

            //Aqui, cria o transform group que vai conter as esferas.
            TransformGroup tgCaixa = new TransformGroup(tfCaixa);
            tgCaixa.addChild(caixa);
            branchGroup.addChild(tgCaixa);

            //Aqui, adiciona o objeto à cada posição do vetor, no caso, todos invertidos para demonstrar o
            vetorNumeros[i] = new Number(j, tgCaixa); //algoritmo inteiro.
            j--;
        }


        return branchGroup;
    }

    private static BranchGroup addBranchGroupEsferas() {
    
        int i;
        BranchGroup branchGroup = new BranchGroup();

        //Aqui, joga a aparencia das esferas na Aparencia atual.
        Appearance aparencia = addAparencia();

        //Aqui, recebe o número de elementos do vetor.
        int j = numero;
        //Aqui vai definir a posicao de cada esfera.
        float posicao = -(raio * numero - raio);

        for(i = 0; i < numero; i++){

            //Cria as caixas com o mesmo raio e a aparência recebida acima.
            //Sphere esfera = new Sphere(raio, aparencia);
            //Box caixa = new Box(raio, raio, raio, aparencia);

            //Aqui, permite que a esfera possa se mover/ser escrita, entre outros.
            //caixa.setCapability(Box.ENABLE_APPEARANCE_MODIFY);

            Sphere esfera = new Sphere(raio, aparencia);

            //Aqui, permite que a esfera possa se mover/ser escrita, entre outros.
            esfera.setCapability(Sphere.ENABLE_APPEARANCE_MODIFY);


            //Aqui, cria os transforms3D de cada esfera e define seu posicionamento,
            Transform3D tfEsfera = new Transform3D(); // logo após, incrementando a distância para a próxima esfera.
            tfEsfera.setTranslation(new Vector3f(posicao, -0.3f, 0.0f));
            posicao += raio * 2 + distancia;

            //Aqui, cria o transform group que vai conter as esferas.
            TransformGroup tgEsfera = new TransformGroup(tfEsfera);
            tgEsfera.addChild(esfera);
            branchGroup.addChild(tgEsfera);

            //Aqui, adiciona o objeto à cada posição do vetor, no caso, todos invertidos para demonstrar o
            vetorNumeros[i] = new Number(j, tgEsfera); //algoritmo inteiro.
            j--;
        }


        return branchGroup;

    }

    public static void bubbleSort(Number v[]) {

        int i;
        //Bubble sort em si.
        for (i = v.length; i >= 1; i--) { //No caso deste for, só vai ser necessária a troca de todos os elementos
        //menos o último da ordenação, porque quando o penúltimo chegar a seu lugar, o primeiro também chegará.
        //No caso, serão ordenados de frente para trás.
            for (int j = 1; j < i; j++) {
                //Aqui, à cada vez que o número de uma posição anterior ao atual, for maior, haverá uma troca.
                if (v[j - 1].getNumber() > v[j].getNumber()) {
                    //Troca da parte gráfica.
                    moverObjeto(v, j - 1, j);
                    //Troca no algoritmo.
                    Number aux = v[j];
                    v[j] = v[j - 1];
                    v[j - 1] = aux;
                    
                }
            }
        }
    }
    
    public static void main(String[] args) {

        JFrame frame = addJanela();
        //Percorre o vetor inteiro e indica-o no terminal do sistema.
        for(Number i : vetorNumeros){
            System.out.println(i.getNumber());
        }

        bubbleSort(vetorNumeros);
        
        for(Number i : vetorNumeros){
            System.out.println(i.getNumber());
        }

    }

    private static void moverObjeto(Number[] v, int n1, int n2) {

        //Aqui, pega os transformgroups dos ítens à serem trocados.
        TransformGroup tgMaior = v[n1].getTGNumber();
        TransformGroup tgMenor = v[n2].getTGNumber();

        //-------------------------------------
        //Pega o transform3D do numero maior.
        Transform3D tfMaior = new Transform3D();
        tgMaior.getTransform(tfMaior);
        //Aqui, pega os valores das cordenadas do numero maior.
        Vector3f translationMaior = new Vector3f();
        tfMaior.get(translationMaior);
        //-------------------------------------

        //-------------------------------------
        //Pega o transform3D do numero menor.
        Transform3D tfMenor = new Transform3D();
        tgMenor.getTransform(tfMenor);
        //Aqui, pega os valores das cordenadas do numero menor.
        Vector3f translationMenor = new Vector3f();
        tfMenor.get(translationMenor);
        //-------------------------------------


        //Vai pegar a posição horizontal do numero menor para ser alterada.
        float xMenor = translationMenor.x;
        //Vai pegar a posição horizontal de onde o número estava para passar para o outro item.
        float xMenorAntigo = translationMenor.x;

        //Vai mover os números menores atuais para a esquerda.
        while(true){
            
            //Fica testando até chegar na posicão de destino.
            if(xMenor <= translationMaior.x){
                break;
            }
            
            //Aqui, define a posição onde vai ficar.
            translationMenor.x = xMenor;
            xMenor -= 0.01f;

            //Faz o posicionamento na tela à partir da nova coordenada.
            tfMenor.setTranslation(translationMenor);
            tgMenor.setTransform(tfMenor);

            //Usa o thread sleep para deixar a animação mais devagar.
            try {
                Thread.sleep(20);
            } catch (InterruptedException ex) {
                Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        float xMaior = translationMaior.x;

        //Vai mover os números maiores atuais para a direita.
        while(true){
            
            if(xMaior >= xMenorAntigo){
                break;
            }
            
            translationMaior.x = xMaior;
            xMaior += 0.01f; 
            tfMaior.setTranslation(translationMaior);
            tgMaior.setTransform(tfMaior);
            
            try {
                Thread.sleep(20);
            } catch (InterruptedException ex) {
                Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        
    }

}
